from django.db import transaction
from django.contrib.auth.models import User
from decimal import Decimal
from typing import Optional

from ..models import Consumable, InventoryLog


class InventoryError(Exception):
    pass


class InventoryService:
    """Service layer for inventory operations (Phase 1: single-warehouse model).

    Methods update `Consumable.quantity` atomically and persist `InventoryLog` with
    `remaining_quantity` for traceability.
    """

    @staticmethod
    @transaction.atomic
    def stock_in(consumable_id: int, qty: Decimal | float | int, operator: Optional[User] = None,
                 description: str = "") -> InventoryLog:
        if qty is None:
            raise InventoryError("Quantity is required")
        qty = Decimal(qty)
        if qty <= 0:
            raise InventoryError("Quantity must be positive for stock in")

        consumable = Consumable.objects.select_for_update().get(pk=consumable_id)
        consumable.quantity = (consumable.quantity or Decimal('0')) + qty
        consumable.save(update_fields=["quantity", "updated_at"])

        log = InventoryLog.objects.create(
            transaction_type='in',
            consumable=consumable,
            quantity=qty,
            remaining_quantity=consumable.quantity,
            operator=operator,
            description=description or "入库"
        )
        return log

    @staticmethod
    @transaction.atomic
    def stock_out(consumable_id: int, qty: Decimal | float | int, operator: Optional[User] = None,
                  description: str = "") -> InventoryLog:
        if qty is None:
            raise InventoryError("Quantity is required")
        qty = Decimal(qty)
        if qty <= 0:
            raise InventoryError("Quantity must be positive for stock out")

        consumable = Consumable.objects.select_for_update().get(pk=consumable_id)
        current = consumable.quantity or Decimal('0')
        if current < qty:
            raise InventoryError("Insufficient stock")

        consumable.quantity = current - qty
        consumable.save(update_fields=["quantity", "updated_at"])

        log = InventoryLog.objects.create(
            transaction_type='out',
            consumable=consumable,
            quantity=qty,
            remaining_quantity=consumable.quantity,
            operator=operator,
            description=description or "出库"
        )
        return log

    @staticmethod
    @transaction.atomic
    def transfer(consumable_id: int, from_warehouse_id: int, to_warehouse_id: int,
                 qty: Decimal | float | int, operator: Optional[User] = None,
                 description: str = ""):
        """Placeholder for transfer under single-warehouse-per-record model.
        In Phase 2 we will switch to per-warehouse stock and implement true transfer.
        For now, validate and perform an out + in on the same record (no warehouse move).
        """
        # Validate by attempting out then in; business-wise doesn't change warehouse yet.
        log_out = InventoryService.stock_out(consumable_id, qty, operator, description or "调拨出库(占位)")
        log_in = InventoryService.stock_in(consumable_id, qty, operator, description or "调拨入库(占位)")
        return log_out, log_in
